﻿/*
 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
 *
 * == BEGIN LICENSE ==
 *
 * Licensed under the terms of any of the following licenses at your
 * choice:
 *
 *  - GNU General Public License Version 2 or later (the "GPL")
 *    http://www.gnu.org/licenses/gpl.html
 *
 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 *    http://www.gnu.org/licenses/lgpl.html
 *
 *  - Mozilla Public License Version 1.1 or later (the "MPL")
 *    http://www.mozilla.org/MPL/MPL-1.1.html
 *
 * == END LICENSE ==
 *
 * This class can be used to interate through nodes inside a range.
 *
 * During interation, the provided range can become invalid, due to document
 * mutations, so CreateBookmark() used to restore it after processing, if
 * needed.
 */

var FCKDomRangeIterator = function( range )
{
    /**
     * The FCKDomRange object that marks the interation boundaries.
     */
    this.Range = range ;

    /**
     * Indicates that <br> elements must be used as paragraph boundaries.
     */
    this.ForceBrBreak = false ;

    /**
     * Guarantees that the iterator will always return "real" block elements.
     * If "false", elements like <li>, <th> and <td> are returned. If "true", a
     * dedicated block element block element will be created inside those
     * elements to hold the selected content.
     */
    this.EnforceRealBlocks = false ;
}

FCKDomRangeIterator.CreateFromSelection = function( targetWindow )
{
    var range = new FCKDomRange( targetWindow ) ;
    range.MoveToSelection() ;
    return new FCKDomRangeIterator( range ) ;
}

FCKDomRangeIterator.prototype =
{
    /**
     * Get the next paragraph element. It automatically breaks the document
     * when necessary to generate block elements for the paragraphs.
     */
    GetNextParagraph : function()
    {
        // The block element to be returned.
        var block ;

        // The range object used to identify the paragraph contents.
        var range ;

        // Indicated that the current element in the loop is the last one.
        var isLast ;

        // Instructs to cleanup remaining BRs.
        var removePreviousBr ;
        var removeLastBr ;

        var boundarySet = this.ForceBrBreak ? FCKListsLib.ListBoundaries : FCKListsLib.BlockBoundaries ;

        // This is the first iteration. Let's initialize it.
        if ( !this._LastNode )
        {
            var range = this.Range.Clone() ;
            range.Expand( this.ForceBrBreak ? 'list_contents' : 'block_contents' ) ;

            this._NextNode = range.GetTouchedStartNode() ;
            this._LastNode = range.GetTouchedEndNode() ;

            // Let's reuse this variable.
            range = null ;
        }

        var currentNode = this._NextNode ;
        var lastNode = this._LastNode ;

        this._NextNode = null ;

        while ( currentNode )
        {
            // closeRange indicates that a paragraph boundary has been found,
            // so the range can be closed.
            var closeRange = false ;

            // includeNode indicates that the current node is good to be part
            // of the range. By default, any non-element node is ok for it.
            var includeNode = ( currentNode.nodeType != 1 ) ;

            var continueFromSibling = false ;

            // If it is an element node, let's check if it can be part of the
            // range.
            if ( !includeNode )
            {
                var nodeName = currentNode.nodeName.toLowerCase() ;

                if ( boundarySet[ nodeName ] && ( !FCKBrowserInfo.IsIE || currentNode.scopeName == 'HTML' ) )
                {
                    // <br> boundaries must be part of the range. It will
                    // happen only if ForceBrBreak.
                    if ( nodeName == 'br' )
                        includeNode = true ;
                    else if ( !range && currentNode.childNodes.length == 0 && nodeName != 'hr' )
                    {
                        // If we have found an empty block, and haven't started
                        // the range yet, it means we must return this block.
                        block = currentNode ;
                        isLast = currentNode == lastNode ;
                        break ;
                    }

                    // The range must finish right before the boundary,
                    // including possibly skipped empty spaces. (#1603)
                    if ( range )
                    {
                        range.SetEnd( currentNode, 3, true ) ;

                        // The found boundary must be set as the next one at this
                        // point. (#1717)
                        if ( nodeName != 'br' )
                            this._NextNode = FCKDomTools.GetNextSourceNode( currentNode, true, null, lastNode ) || currentNode ;
                    }

                    closeRange = true ;
                }
                else
                {
                    // If we have child nodes, let's check them.
                    if ( currentNode.firstChild )
                    {
                        // If we don't have a range yet, let's start it.
                        if ( !range )
                        {
                            range = new FCKDomRange( this.Range.Window ) ;
                            range.SetStart( currentNode, 3, true ) ;
                        }

                        currentNode = currentNode.firstChild ;
                        continue ;
                    }
                    includeNode = true ;
                }
            }
            else if ( currentNode.nodeType == 3 )
            {
                // Ignore normal whitespaces (i.e. not including &nbsp; or
                // other unicode whitespaces) before/after a block node.
                if ( /^[\r\n\t ]+$/.test( currentNode.nodeValue ) )
                    includeNode = false ;
            }

            // The current node is good to be part of the range and we are
            // starting a new range, initialize it first.
            if ( includeNode && !range )
            {
                range = new FCKDomRange( this.Range.Window ) ;
                range.SetStart( currentNode, 3, true ) ;
            }

            // The last node has been found.
            isLast = ( ( !closeRange || includeNode ) && currentNode == lastNode ) ;
//            isLast = ( currentNode == lastNode && ( currentNode.nodeType != 1 || currentNode.childNodes.length == 0 ) ) ;

            // If we are in an element boundary, let's check if it is time
            // to close the range, otherwise we include the parent within it.
            if ( range && !closeRange )
            {
                while ( !currentNode.nextSibling && !isLast )
                {
                    var parentNode = currentNode.parentNode ;

                    if ( boundarySet[ parentNode.nodeName.toLowerCase() ] )
                    {
                        closeRange = true ;
                        isLast = isLast || ( parentNode == lastNode ) ;
                        break ;
                    }

                    currentNode = parentNode ;
                    includeNode = true ;
                    isLast = ( currentNode == lastNode ) ;
                    continueFromSibling = true ;
                }
            }

            // Now finally include the node.
            if ( includeNode )
                range.SetEnd( currentNode, 4, true ) ;

            // We have found a block boundary. Let's close the range and move out of the
            // loop.
            if ( ( closeRange || isLast ) && range )
            {
                range._UpdateElementInfo() ;

                if ( range.StartNode == range.EndNode
                        && range.StartNode.parentNode == range.StartBlockLimit
                        && range.StartNode.getAttribute && range.StartNode.getAttribute( '_fck_bookmark' ) )
                    range = null ;
                else
                    break ;
            }

            if ( isLast )
                break ;

            currentNode = FCKDomTools.GetNextSourceNode( currentNode, continueFromSibling, null, lastNode ) ;
        }

        // Now, based on the processed range, look for (or create) the block to be returned.
        if ( !block )
        {
            // If no range has been found, this is the end.
            if ( !range )
            {
                this._NextNode = null ;
                return null ;
            }

            block = range.StartBlock ;

            if ( !block
                && !this.EnforceRealBlocks
                && range.StartBlockLimit.nodeName.IEquals( 'DIV', 'TH', 'TD' )
                && range.CheckStartOfBlock()
                && range.CheckEndOfBlock() )
            {
                block = range.StartBlockLimit ;
            }
            else if ( !block || ( this.EnforceRealBlocks && block.nodeName.toLowerCase() == 'li' ) )
            {
                // Create the fixed block.
                block = this.Range.Window.document.createElement( FCKConfig.EnterMode == 'p' ? 'p' : 'div' ) ;

                // Move the contents of the temporary range to the fixed block.
                range.ExtractContents().AppendTo( block ) ;
                FCKDomTools.TrimNode( block ) ;

                // Insert the fixed block into the DOM.
                range.InsertNode( block ) ;

                removePreviousBr = true ;
                removeLastBr = true ;
            }
            else if ( block.nodeName.toLowerCase() != 'li' )
            {
                // If the range doesn't includes the entire contents of the
                // block, we must split it, isolating the range in a dedicated
                // block.
                if ( !range.CheckStartOfBlock() || !range.CheckEndOfBlock() )
                {
                    // The resulting block will be a clone of the current one.
                    block = block.cloneNode( false ) ;

                    // Extract the range contents, moving it to the new block.
                    range.ExtractContents().AppendTo( block ) ;
                    FCKDomTools.TrimNode( block ) ;

                    // Split the block. At this point, the range will be in the
                    // right position for our intents.
                    var splitInfo = range.SplitBlock() ;

                    removePreviousBr = !splitInfo.WasStartOfBlock ;
                    removeLastBr = !splitInfo.WasEndOfBlock ;

                    // Insert the new block into the DOM.
                    range.InsertNode( block ) ;
                }
            }
            else if ( !isLast )
            {
                // LIs are returned as is, with all their children (due to the
                // nested lists). But, the next node is the node right after
                // the current range, which could be an <li> child (nested
                // lists) or the next sibling <li>.

                this._NextNode = block == lastNode ? null : FCKDomTools.GetNextSourceNode( range.EndNode, true, null, lastNode ) ;
                return block ;
            }
        }

        if ( removePreviousBr )
        {
            var previousSibling = block.previousSibling ;
            if ( previousSibling && previousSibling.nodeType == 1 )
            {
                if ( previousSibling.nodeName.toLowerCase() == 'br' )
                    previousSibling.parentNode.removeChild( previousSibling ) ;
                else if ( previousSibling.lastChild && previousSibling.lastChild.nodeName.IEquals( 'br' ) )
                    previousSibling.removeChild( previousSibling.lastChild ) ;
            }
        }

        if ( removeLastBr )
        {
            var lastChild = block.lastChild ;
            if ( lastChild && lastChild.nodeType == 1 && lastChild.nodeName.toLowerCase() == 'br' )
                block.removeChild( lastChild ) ;
        }

        // Get a reference for the next element. This is important because the
        // above block can be removed or changed, so we can rely on it for the
        // next interation.
        if ( !this._NextNode )
            this._NextNode = ( isLast || block == lastNode ) ? null : FCKDomTools.GetNextSourceNode( block, true, null, lastNode ) ;

        return block ;
    }
} ;
